package cn.yeziji.forum.service.impl;

import cn.yeziji.forum.common.key.CommonKey;
import cn.yeziji.forum.dao.intf.ForumFileDao;
import cn.yeziji.forum.exception.UploadException;
import cn.yeziji.forum.service.UploadFileService;
import cn.yeziji.forum.common.enums.status.UploadStatus;
import cn.yeziji.forum.utils.StrUtils;
import cn.yeziji.forum.utils.ThreadPoolConfigUtils;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * 上传文件服务逻辑实现层
 *
 * @author gzkemays
 * @since 2022/3/28 23:19
 */
@Service
@Slf4j
public class UploadFileServiceImpl implements UploadFileService {
  @Resource ForumFileDao fileDao;

  @Resource(name = "fileMd5")
  Cache<String, String> md5Cache;

  @Override
  public List<String> uploadChatRoomFile(MultipartFile[] files) {
    if (files.length > UPLOAD_MAX) {
      throw new UploadException(UploadStatus.UPLOAD_MAX_FAIL);
    }
    List<FileUploadStatus> fileUploadStatuses = checkFilesExists(files);
    if (files.length > OPEN_THREAD_SIZE) {
      return threadUpload(fileUploadStatuses, ForumFileDao.CHATROOM_IMG);
    }
    return singleUpload(fileUploadStatuses, ForumFileDao.CHATROOM_IMG);
  }

  @Override
  public List<String> uploadDetailFile(MultipartFile[] files) {
    List<String> urls = new ArrayList<>();
    for (MultipartFile file : files) {
      urls.add(upload(file, ForumFileDao.DETAIL_IMG));
    }
    return urls;
  }

  private List<String> threadUpload(List<FileUploadStatus> files, String mode) {
    List<String> urls = new ArrayList<>();
    List<Future<String>> futures = new ArrayList<>();
    ThreadPoolConfigUtils.builder()
        .createThreadPool()
        .corePoolSize(3)
        .maximumPoolSize(6)
        .operation(
            (e) -> {
              for (FileUploadStatus file : files) {
                Future<String> submit =
                    e.submit(
                        () -> {
                          if (!file.isUpload()) {
                            return upload(file.getFile(), mode);
                          } else {
                            return file.getUrl();
                          }
                        });
                futures.add(submit);
              }
            })
        .build();
    futures.forEach(
        future -> {
          try {
            urls.add(future.get());
          } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
          }
        });
    return urls;
  }

  private List<String> singleUpload(List<FileUploadStatus> files, String mode) {
    List<String> urls = new ArrayList<>();
    for (FileUploadStatus file : files) {
      if (!file.isUpload()) {
        MultipartFile f = file.getFile();
        urls.add(upload(f, mode));
      } else {
        urls.add(file.getUrl());
      }
    }
    return urls;
  }

  /**
   * 上传文件
   *
   * @param f multipartfile
   * @param mode 上传类型，参考 {@link ForumFileDao} 下的变量
   * @return 文件访问地址
   */
  private String upload(MultipartFile f, String mode) {
    try {
      String filename = DigestUtils.md5DigestAsHex(f.getInputStream());
      log.info("{} --> 进行上传", filename);
      String url = fileDao.saveFile(f, mode);
      md5Cache.put(filename, url);
      return url;
    } catch (IOException e) {
      e.printStackTrace();
    }
    throw new UploadException(UploadStatus.UPLOAD_FAIL);
  }

  /**
   * 判断文件是否已存在
   *
   * @param files 文件列表
   * @return 文件上传状态
   */
  private List<FileUploadStatus> checkFilesExists(MultipartFile[] files) {
    List<FileUploadStatus> urls = new ArrayList<>();
    for (MultipartFile file : files) {
      try {
        String filename = DigestUtils.md5DigestAsHex(file.getInputStream());
        String ifPresent = md5Cache.getIfPresent(filename);
        if (ifPresent != null) {
          urls.add(new FileUploadStatus(filename, ifPresent, true, file));
        } else {
          // 可能缓存过期
          String expectUrl =
              TEMP_UPLOAD_PREFIX
                  + filename
                  + StrUtils.getLastSplitHasSplitAfterStr(
                      file.getOriginalFilename(), CommonKey.DOT);
          log.info("expect url --> {}", expectUrl);
          URL url = new URL(expectUrl);
          trustAllHosts();
          // 发送请求进行判断
          HttpURLConnection connection = (HttpURLConnection) url.openConnection();
          connection.setRequestMethod("GET");
          connection.setUseCaches(false);
          connection.setDoOutput(true);
          connection.setDoInput(true);
          int responseCode = connection.getResponseCode();
          if (responseCode == HttpURLConnection.HTTP_OK) {
            urls.add(new FileUploadStatus(filename, expectUrl, true, file));
          } else {
            urls.add(new FileUploadStatus(filename, null, false, file));
          }
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return urls;
  }

  private void trustAllHosts() {
    TrustManager[] trustAllCerts =
        new TrustManager[] {
          new X509TrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
              return new java.security.cert.X509Certificate[] {};
            }

            @Override
            public void checkClientTrusted(
                java.security.cert.X509Certificate[] chain, String authType)
                throws CertificateException {}

            @Override
            public void checkServerTrusted(
                java.security.cert.X509Certificate[] chain, String authType)
                throws CertificateException {}
          }
        };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL", "SunJSSE");
      sc.init(null, trustAllCerts, new java.security.SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(NoopHostnameVerifier.INSTANCE);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
